package com.yookey.common.utils;

import com.alibaba.fastjson.JSON;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.yookey.model.dto.AccountDTO;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * className JWTUtil
 *
 * @Author: Zhang Shao Hui
 * @Description:
 * @Date:Create：in 2019/11/20 15:16
 * @Modified By：
 */
@Configuration
public class JWTUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(JWTUtil.class);


    private static String secretKey;

    private static long tokenExpireTime;


    private static long refreshTokenExpireTime;


    private static String jwtBlacklistKeyFormat;


    private static String jwtRefreshTokenKeyFormat;


    private static StringRedisTemplate stringRedisTemplate;

    @Value("${jwt.secret.key}")
    public void setSecretKey(String secretKey) {
        this.secretKey = secretKey;
    }

    @Value("${token.expire.time}")
    public void setTokenExpireTime(long tokenExpireTime) {
        this.tokenExpireTime = tokenExpireTime;
    }

    @Value("${refresh.token.expire.time}")
    public void setRefreshTokenExpireTime(long refreshTokenExpireTime) {
        this.refreshTokenExpireTime = refreshTokenExpireTime;
    }

    @Value("${jwt.blacklist.key.format}")
    public void setJwtBlacklistKeyFormat(String jwtBlacklistKeyFormat) {
        this.jwtBlacklistKeyFormat = jwtBlacklistKeyFormat;
    }

    @Value("${jwt.refresh.token.key.format}")
    public void setJwtRefreshTokenKeyFormat(String jwtRefreshTokenKeyFormat) {
        this.jwtRefreshTokenKeyFormat = jwtRefreshTokenKeyFormat;
    }


    @Autowired
    public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
        JWTUtil.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 构建JWT
     * @param accountDTO
     * @return
     */
    private static String buildJWT(AccountDTO accountDTO) {
        Date now = new Date();
        Algorithm algo = Algorithm.HMAC256(secretKey);
        String token = JWT.create()
                .withIssuer("yookey")
                .withIssuedAt(now)
                .withAudience()
                .withClaim("data", JSON.toJSONString(accountDTO))
                .withExpiresAt(new Date(now.getTime() + tokenExpireTime))
                //编号
                .withJWTId(accountDTO.getRefreshToken())
                .sign(algo);
        return token;
    }

    /**
     * 生成token
     * @param accountDTO
     * @return
     */
    public static String generateToken(AccountDTO accountDTO) {
        //生成jwt JWT 随机ID,做为验证的key
        String refreshToken = UUID.randomUUID().toString();
        String refreshTokenKey = String.format(jwtRefreshTokenKeyFormat, refreshToken);
        accountDTO.setRefreshToken(refreshToken);
        String token = buildJWT(accountDTO);
        accountDTO.setToken(token);
        stringRedisTemplate.opsForValue().set(refreshTokenKey, JSON.toJSONString(accountDTO));
        stringRedisTemplate.expire(refreshTokenKey, refreshTokenExpireTime, TimeUnit.MINUTES);

        return token;
    }

    /**
     * 刷新token
     * @param refreshToken
     * @return
     */
    public static Map<String, Object> refreshToken(String refreshToken) {
        Map<String, Object> resultMap = new HashMap<>();
        String refreshTokenKey = String.format(jwtRefreshTokenKeyFormat, refreshToken);
        String loginInfo = stringRedisTemplate.opsForValue().get(refreshTokenKey);
        if (StringUtils.isBlank(loginInfo)) {
            AssertUtil.throwException("401", "token过期");
        }
        AccountDTO accountDTO = JSON.parseObject(loginInfo, AccountDTO.class);
        String oldToken = accountDTO.getToken();
        String newToken = buildJWT(accountDTO);
        accountDTO.setToken(newToken);
        stringRedisTemplate.opsForValue().set(refreshTokenKey, JSON.toJSONString(accountDTO));
        //替换当前token，并将旧token添加到黑名单
        stringRedisTemplate.opsForValue().set(String.format(jwtBlacklistKeyFormat, oldToken), "",
                tokenExpireTime, TimeUnit.MINUTES);
        //返回结果
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("token", newToken);
        //dataMap.put("refreshToken", refreshToken);
        return resultMap;
    }

    /**
     * 退出
     * @param refreshToken
     */
    public static void logoutToken(String refreshToken) {
        String refreshTokenKey = String.format(jwtRefreshTokenKeyFormat, refreshToken);
        String loginInfo = stringRedisTemplate.opsForValue().get(refreshTokenKey);
        if (StringUtils.isBlank(loginInfo)) {
            AssertUtil.throwException("401", "token过期");
        }
        AccountDTO accountDTO = JSON.parseObject(loginInfo, AccountDTO.class);
        String oldToken = accountDTO.getToken();
        //替换当前token，并将旧token添加到黑名单
        stringRedisTemplate.opsForValue().set(String.format(jwtBlacklistKeyFormat, oldToken), "",
                tokenExpireTime, TimeUnit.MINUTES);
    }

    public static String verifyTokenRenewal(String token, String refreshToken) {
        try {
            //1 . 根据token解密，解密出jwt-id , 先从redis中查找出redisToken，匹配是否相同
            String refreshTokenKey = String.format(jwtRefreshTokenKeyFormat, refreshToken);

            AccountDTO accountDTO = null;
            String loginInfo = stringRedisTemplate.opsForValue().get(refreshTokenKey);
            if (StringUtils.isBlank(loginInfo)) {
                return null;
            } else {
                accountDTO = JSON.parseObject(loginInfo, AccountDTO.class);
                if (accountDTO != null) {
                    if (!accountDTO.getToken().equals(token)) {
                        return null;
                    }
                } else {
                    return null;
                }
            }
            Algorithm algorithm = Algorithm.HMAC256(secretKey);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withIssuer("yookey")
                    .acceptExpiresAt(System.currentTimeMillis() + tokenExpireTime * 60 * 1000)//配置续期
                    .withJWTId(accountDTO.getRefreshToken())
                    .build();
            verifier.verify(accountDTO.getToken());
            //4 . Redis缓存JWT续期
            stringRedisTemplate.expire(refreshTokenKey, tokenExpireTime, TimeUnit.MINUTES);
        } catch (JWTVerificationException e) {
            LOGGER.error(e.getMessage(), e);
            return null;
        }
        return token;
    }

    public static String verifyToken(String token) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(secretKey);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withIssuer("yookey")
                    .build();
            verifier.verify(token);
        } catch (JWTVerificationException e) {
            LOGGER.error(e.getMessage(), e);
            return null;
        }
        return token;
    }

    public static Boolean hasKey(String jwtId) {
        return stringRedisTemplate.hasKey(String.format(jwtBlacklistKeyFormat, jwtId));
    }

    /**
     * 判断token是否在黑名单内
     *
     * @param token
     * @return
     */
    public static boolean isBlackToken(String token) {
        assert token != null;
        return stringRedisTemplate.hasKey(String.format(jwtBlacklistKeyFormat, token));
    }

    public static Map<String, Claim> getClaims(String token) {
        return JWT.decode(token).getClaims();

    }
}
